superstrict

import BRL.Blitz
import BRL.PolledInput
import BRL.KeyCodes
import BRL.Map
import BRL.LinkedList
import BRL.FreeTypeFont
import BRL.PolledInput
import BRL.GLMax2D
import BRL.Retro
import Pub.Glew

' Console by Odin Jensen
' Use as you please :)
' Thanks to Perturbatio for the OpenConsole source.

Type TConsole

	' Console active flag
	Field isActive% = False
	
	' Command map
	Field commands:TMap = New TMap
	
	' Passive console text
	Field passiveText:TList = New TList
	
	' Active console text
	Field conText:TList = New TList
	
	' Command history
	Field commandHistory:TList = New TList
	
	' Size
	Field conWidth%, conLines%
	
	' Console list position
	Field conPos% = 0
	
	' Passive console list position
	Field conPassivePos% = 0
	
	' Input field
	Field inputField$ = ""
	
	' Cursorpos
	Field cursorPos% = 0
	
	' History pos
	Field historyPos% = -1

	' Init console
	Method Init(width%, lines%)
	
		' Save size
		conWidth = width
		conLines = lines
			
		?debug
		
			AddPassiveConsoleText("*** Debug build ***")
			AddActiveConsoleText("*** Debug build ***")					
		?
		
		
		AddPassiveConsoleText("Press <TAB> to activate console")
		AddActiveConsoleText("Press <TAB> to hide console")

		AddActiveConsoleText("Type 'cmdlist' for list of commands.")
		
	
	End Method
	
	' Is active?
	Method Active%()
	
		Return isActive
	
	End Method
	
	' Update console
	Method Update()
	
		' Set text color
		SetColor(255,255,255)	
	
		' Check for console activation key
		If (KeyHit(KEY_TAB))
		
			isActive = Not isActive
			FlushKeys()
		
		End If
		
		' Active?
		If (isActive)
				
			' Draw console
			SetColor(0,0,255)
			DrawRect(0, 0, conWidth, conLines*12)
			
			SetColor(255,255,255)	
			
			' Draw input string
			DrawText(inputField, 5, 5)

			' Draw cursor			
			DrawText("_", TextWidth(inputField[..CursorPos+1])-2, 5)

			
			' Draw active console text
			Local curLine%=0
			For Local i% = conPos To conText.Count()-1
			
				DrawText(String(conText.ValueAtIndex(i)), 5, 5+12+(curLine*12))
				curLine:+1
				If (curLine > conLines-3)
				
					Exit
				
				End If
			
			Next		
			
			' Draw scroll markers?
			If (conPos > 0)
			
				DrawText("<<", conWidth-(12*2), 0)
			
			End If
			If (conPos +22<=conText.Count())
			
				DrawText(">>", conWidth-(12*2), (conLines-1)*12)

			End If
		
		
		Else
		
			' Draw passive console text
			Local curLine%=0
			For Local i% = conPassivePos To passiveText.Count()-1
			
				DrawText(String(passiveText.ValueAtIndex(i)), 5, 5+(curLine*12))
				curLine:+1
				If (curLine > conLines)
				
					Exit
				
				End If
			
			Next
			
			' Draw scroll markers?
			If (conPassivePos > 0)
			
				DrawText("<<", conWidth-(12*2), 0)
			
			End If
			If (conPassivePos+22<=passiveText.Count())
			
				DrawText(">>", conWidth-(12*2), conLines*12)

			End If
		
		End If
		
		' Do an input check
		CheckInput()
	
	End Method
	
	' Check input
	Method CheckInput()
	
		' Active?
		If (isActive)
		
			' Do passive scroll?
			If (KeyHit(KEY_PAGEUP))
				
				If (conPos > 0)
					conPos :-1
				End If			
			
			ElseIf (KeyHit(KEY_PAGEDOWN))
			
				If (conPos <= (conText.Count()-2-conLines))
					conPos :+1
				End If	
			
			End If
			
			Local ch:Int = GetChar()
		
			If ((ch > 31) And (ch < 126))
				inputField=inputField[..CursorPos]+Chr(ch)+inputField[CursorPos..]':+Chr(ch)
				CursorPos:+1
			EndIf
			
			Select (ch)
				Case 8 'Backspace
					If (CursorPos > 0)
					
						inputField=inputField[..CursorPos-1]+inputField[CursorPos..] 'backspace
						CursorPos:-1
						
					EndIf
				
				Case 13 'Return
					If (Len(inputField)>0)
					
						Local cmdList:TList = SplitString(inputField, " ")
						Local args:String[] = New String[cmdList.Count()-1]
						Local cmd:String = String(cmdList.ValueAtIndex(0))
						
						If (cmdList.Count() > 1)
						
							For Local i%=1 To cmdList.Count()-1
							
								args[i-1] = String(cmdList.ValueAtIndex(i)).toLower()
							
							Next
							
						End If
						ExecuteCommand(cmd, args)
						commandHistory.AddFirst(inputField)						
						
					EndIf
					
					inputField = "" 'return
					CursorPos = 0
					historyPos = -1
								
				Case 27 'Escape
				
					inputField = "" 'escape
					CursorPos = 0	
					historyPos = -1				
					
			End Select
			
			' Check special keys
			Local SpecialKey% = GetKey()
			
			Select (SpecialKey)
			
				Case KEY_LEFT 
				
					If (CursorPos>0) CursorPos:- 1
					
				Case KEY_RIGHT
				
					If (CursorPos < Len(inputField)) CursorPos:+ 1
					
				Case KEY_DELETE
				
					If (CursorPos < Len(inputField))
						inputField= inputField[..CursorPos]+inputField[CursorPos+1..]
					EndIf
					
				Case KEY_END
				
					CursorPos = Len(inputField)
					
				Case KEY_HOME
				
					CursorPos = 0		
					
				Case KEY_UP
				
					If (commandHistory.Count() > 0)
						If (historyPos < commandHistory.Count()-1) historyPos:+ 1
						inputField= String(commandHistory.ValueAtIndex(historyPos))
						CursorPos = Len(inputField)
					EndIf
				
				Case KEY_DOWN
				
					If (commandHistory.Count() > 0)
					If (historyPos > -1) HistoryPos:- 1
					If (historyPos < 0)
						inputField= ""
					Else
						inputField= String(commandHistory.ValueAtIndex(historyPos ))
					EndIf
					CursorPos = Len(inputField)
					
				EndIf

					
			End Select						
		
		Else
		
			' Do passive scroll?
			If (KeyHit(KEY_PAGEUP))
				
				If (conPassivePos > 0)
					conPassivePos:-1
				End If			
			
			ElseIf (KeyHit(KEY_PAGEDOWN))
			
				If (conPassivePos <= (passiveText.count()-2-conLines))
					conPassivePos:+1
				End If	
			
			End If
			
		
		End If
			
	End Method
	
	' Add command
	Method AddCommand(command$, help$, Action(parms$[]))
	
		Local newCommand:TConsoleCommand = New TConsoleCommand
		newCommand.desc = help
		newCommand.Action = Action
		MapInsert(commands, command.toLower(), newCommand)
	
	End Method
	
	' Execute command
	Method ExecuteCommand(command$, parms$[])
		
		Local cmd:TConsoleCommand = TConsoleCommand(MapValueForKey(commands, command.toLower()))
		If (cmd = Null)
		
			AddActiveConsoleText("Unknown command: " + command + "!")
			Return
		
		End If
		
		cmd.Action(parms)
		
	
	End Method
	
	' Add text to active console
	Method AddActiveConsoleText(text$)
	
		conText.addLast(text)
		If (conText.Count() > conLines-1)
		
			conPos:+1
		
		End If

	End Method
	
	' Add text to passive console
	Method AddPassiveConsoleText(text$)
	
		passiveText.addLast(text)
		If (passiveText.Count() > conLines)
		
			conPassivePos:+1
		
		End If
	
	End Method
	
	' Add to visible console
	Method AddVisibleConsoleText(text$)
	
		If (isActive)
		
			AddActiveConsoleText(text)
		
		Else
		
			AddPassiveConsoleText(text)
		
		End If
	
	End Method
	
	' *** Commands ***
	
	' List commands
	Method cmdListCommands()
	
		Local i:TMapEnumerator = commands.Values()
		For Local curCmd:TConsoleCommand=EachIn i
		
			AddActiveConsoleText(curCmd.desc)
		
		Next
	
	End Method
	
	' Clear console
	Method cmdClearConsole(parms$[])
	
		If (parms.length > 0)
		
			Select (parms[0])
			
				Case "active"
				
					conPos = 0
					conText.clear()
					commandHistory.clear()
					Return
				
				Case "passive"
				
					conPassivePos = 0
					passiveText.clear()
					Return								
			
			End Select
		
		End If
		
		conPassivePos = 0
		conPos = 0
		passiveText.clear()
		conText.clear()
	
	End Method
	
	' Echo text
	Method cmdEcho(parms$[])
	
		If (parms.length = 0)
		
			AddActiveConsoleText("You need to specify some text to echo.")
			Return
						
		End If
		
		Local out$ = ""
		For Local i%=0 To parms.length-1
		
			out:+" "+parms[i]
		
		Next
		
		AddActiveConsoleText(out[1..out.length])
	
	End Method
	
	' Dump console
	Method cmdDumpConsole(parms$[])
	
		Local fh:TStream = WriteFile("ConsoleDump.txt")
		If (fh)
		
			WriteLine(fh, "[Passive Console]")
			For Local pct:String=EachIn passiveText
			
				WriteLine(fh, pct)
			
			Next
			
			WriteLine(fh, "~n[Active Console]")
			For Local pct:String=EachIn conText
			
				WriteLine(fh, pct)
			
			Next

		
			CloseStream(fh)
			
			AddActiveConsoleText("Dumped consoles to 'ConsoleDump.txt'.")
		
		Else
		
			AddActiveConsoleText("Could not write 'ConsoleDump.txt'. Write protected media?")
		
		End If
	
	End Method
	
	' Execute script command
	Method cmdExecFile(args$[])
	
		If (args.length = 0)
		
			AddActiveConsoleText("You need to specify a file to execute.")
			Return
						
		End If
		
		Local fh:TStream = OpenFile(args[0])
		If (fh = Null)
		
			AddActiveConsoleText("File '" + args[0] + "' not found!")
			Return
		
		End If
		
		While (Not Eof(fh))
		
			Local cmdList:TList = SplitString(ReadLine(fh), " ")
			Local fargs:String[] = New String[cmdList.Count()-1]
			Local cmd:String = String(cmdList.ValueAtIndex(0))
			
			If (cmdList.Count() > 1)
			
				For Local i%=1 To cmdList.Count()-1
				
					fargs[i-1] = String(cmdList.ValueAtIndex(i)).toLower()
				
				Next
				
			End If

			ExecuteCommand(cmd, fargs)
		
		Wend

		CloseStream(fh)
	
	End Method
	
	
End Type

' Console command type
Type TConsoleCommand

	Field desc:String
	Field Action(parms$[])

End Type

private

' Split string used for splitting console commands and args
Function SplitString:TList(inString:String, Delim:String)
	Local tempList : TList = New TList
	Local currentChar : String = ""
	Local count : Int = 0
	Local TokenStart : Int = 0
	
	If Len(Delim)<>1 Then Return Null
	
	inString = Trim(inString)
	
	For count = 0 Until Len(inString)
		If inString[count..count+1] = delim Then
			tempList.AddLast(inString[TokenStart..Count])
			TokenStart = count + 1
		End If
	Next
	tempList.AddLast(inString[TokenStart..Count])	
	Return tempList
End Function


' Get key utility function
Function GetKey:Int()

	Const RepeatRate : Int = 100 'ms
	Global LastTime:Long = MilliSecs()
	Global LastKey:Int = 0
	Local key:Int = 0
		
	While  Key <= 226
		If KeyDown(key) Then Exit
		Key:+ 1
	Wend
	
	
	If Key = 227 Then Return 0
	
	If Key = LastKey Then
		If MilliSecs()-LastTime < RepeatRate Then Return 0
	EndIf
	
	LastTime = MilliSecs()
	LastKey = Key
	If Key < 227 Then Return Key Else Return 0
	
End Function
